﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using FinPlusInterfaces;

namespace FinPlusAssembler
{
    //Simple IOC to decouple core base classes for early dev at point of prod release
    //core bases classes can be recoupled re Pods Pod, Beans etc these classes can 
    //be transient. Other class eg conn etc remain decoupled. Main assembly 
    //which should be recoupled is FinPlusCoreBase. IOC caches compiled expressions 
    //and the permformace is good so its your choice.
    public static class IOC 
    {
        private volatile static ConcurrentDictionary<string, IPods> _regTypes;
        private volatile static object _lock = new Object();

        //construct
        static IOC(){ Initialise();}

        //public
        public static Type FindType(string type)
        {
            Type res = null;
            if (type.StartsWith("System."))
                res = Type.GetType(type);
            else
            {
                var assembly = Paths.Values["FolderRoot"].GetAssembly(type.Split('.')[0]);
                if(assembly != null)
                    res = assembly.GetType(type);
            }

            return res;
        }

        public static T Cast<T>(object value)
        {
            var type = typeof(T).RegType();

            return (T)Convert.ChangeType(value, type.Type);
        }

        public static T New<T>(params object[] parms)
        {
            try
            {
                var type = typeof(T).RegType();
                var obj = type.Type.New<T>( parms);
                return (T)obj;
            }
            catch (Exception e)
            {
                throw new AssemblerException(string.Format("IOC failed to constuct registered type {0}", (typeof(T)).Name, e));
            }
        }

        public static T New<T>(this Type type, object[] parms)
        {
            try
            {
                var obj = type.Construct<T>(parms);
                //var obj = Activator.CreateInstance(type, parms);

                return (T)obj;
            }
            catch (Exception e)
            {
                throw new AssemblerException(string.Format("IOC failed to constuct non registered type {0}",type.Name, e));
            }
        }

        public static T Singleton<T>(params object[] parms)
        {
            try
            {
                var type = typeof(T).RegType();
                if (type.Object == null)
                    lock (_lock)
                        if (type.Object == null)
                            type.Object = type.Type.Construct<T>(parms);
                            //type.Object = Activator.CreateInstance(type.Type, parms);

                return (T)type.Object;
            }
            catch (Exception e)
            {
                throw new AssemblerException("IOC failed to constuct registered singleton", e);
            }
        }

        //private
        private static void Initialise()
        {
            try
            {
                _regTypes = new ConcurrentDictionary<string, IPods>();
             
                var path = Assembly.GetExecutingAssembly().Path();
                var files = Directory.GetFiles(Paths.Values["FolderRegInterfaces"], "*." + FileType.reginter.ToString());

                foreach (string file in files)
                {
                    var res = file.Split('\\');
                    var regInter = res[res.Length - 1].Replace("." + FileType.reginter.ToString(), "");
                    if (!_regTypes.ContainsKey(regInter))
                    {
                        var regInterfaces = FindType("FinPlusBaseCore.Pods").New<IPods>(new object[] { file });
                        _regTypes[regInter] = regInterfaces;
                        regInterfaces.RegisterTypes();
                    }
                }
            }
            catch (Exception e)
            {
                throw new TemplateException(string.Format("IOC reg interface error", e));
            }
        }

        private static void RegisterTypes(this IPods regTypes)
        {
            foreach (var regType in regTypes.Values)
                regType.Type = FindType(regType.Get("Type").ToString());
        }

        private static IPod RegType(this Type value) { return RegType(value.FullName); }

        private static IPod RegType(string value)
        {
            var name = value.Split('.');

            IPods regInterface;
            if (!_regTypes.TryGetValue(name[0], out regInterface))
                throw new AssemblerException(string.Format("IOC interface {0} not registered", value));

            var regType = regInterface.Get(name[1]);
            if (regType == null || regType.Type == null)
                throw new AssemblerException(string.Format("IOC interface {0} not registered", value));

            return regType;
        }
    }
}
